#!/usr/bin/python3
#
# Script for rendering Jinja2 templates.
#
# Copyright (C) 2022 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.  You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 31 Milk Street #960789 Boston, MA
# 02196 USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import os

from shutil import copystat
from argparse import ArgumentParser
from jinja2 import Environment, BaseLoader, FileSystemLoader, StrictUndefined
from yaml import safe_load # pyyaml

CONFIG_PATH = ".branch-variables.yml"

# Jinja delimiters override.
BLOCK_START_STRING = "{%"
BLOCK_END_STRING = "%}"
VARIABLE_START_STRING = "{$"
VARIABLE_END_STRING = "$}"

DISCLAIMER = """\
# ======================================
# WARNING!
# THIS FILE IS GENERATED FROM A TEMPLATE
# DO NOT EDIT THIS FILE MANUALLY!
# ======================================
# The template is located in: {$ template_file_name $}\n
"""


def parse_args():
    """Parse command line arguments for quick configuration override."""
    parser = ArgumentParser(description="Render a jinja template.")
    parser.add_argument("input", help="Path to jinja template.")
    parser.add_argument("output", help="Path to save the rendered template.")
    parser.add_argument("--variables", help="Path to the Jinja variables file.")

    config = parser.parse_args()
    return vars(config)


def make_env(loader):
    """Create a jinja environment with a given loader."""
    env = Environment(
        loader=loader,
        keep_trailing_newline=True,
        undefined=StrictUndefined,
        block_start_string=BLOCK_START_STRING,
        block_end_string=BLOCK_END_STRING,
        variable_start_string=VARIABLE_START_STRING,
        variable_end_string=VARIABLE_END_STRING,
        trim_blocks=True,
        lstrip_blocks=True,
    )
    return env


def render_file(config, variables):
    """Renders a file as a Jinja2 template and populates it with variables."""
    env = make_env(FileSystemLoader(os.path.dirname(config["input"])))
    content = env.get_template(os.path.basename(config["input"]))
    content = content.render(variables)

    return content


def render_string(string: str, variables) -> str:
    """Renders a given string as a Jinja2 template and populates it with variables."""
    env = make_env(BaseLoader)
    template = env.from_string(string)
    content = template.render(variables)
    return content


def write_file(content: str, input_file: str, output_file: str):
    """Writes given content to a given path."""
    with open(output_file, "w", encoding="utf8") as file:
        file.write(content)

    copystat(input_file, output_file)


def load_file(path: str) -> str:
    """Outputs contents of a file at given path"""
    with open(path, "r", encoding="utf8") as file:
        return file.read()


def remove_empty_result(path: str):
    """Remove empty result of conversion"""
    print("Empty result, removing {}".format(path))
    if os.path.exists(path):
        os.remove(path)


def add_disclaimer(disclaimer: str, content: str) -> str:
    """Add disclaimer to rendered content

    If a hashbang is present, keep it at the beginning of result.
    """
    has_hashbang = content.startswith("#!")

    if not has_hashbang:
        return disclaimer + content

    content_lines = content.splitlines(keepends=True)
    return content_lines[0] + disclaimer + "".join(content_lines[1:])


def main():
    """Entry point of the script."""
    config = parse_args()
    var_path = config["variables"] or CONFIG_PATH
    variables = safe_load(load_file(var_path))

    variables.update({"template_file_name": os.path.basename(config["input"])})

    print("Loading template {}".format(config["input"]))
    rendered_file = render_file(config, variables)

    # delete files that end up completely empty
    if not rendered_file.strip():
        remove_empty_result(config["output"])
        return

    rendered_disclaimer = render_string(DISCLAIMER, variables)
    content = add_disclaimer(rendered_disclaimer, rendered_file)
    print("Saving rendered result to {}".format(config["output"]))
    write_file(content, config["input"], config["output"])


if __name__ == "__main__":
    main()
